// IPv4ClientSocket.cpp
//
// Copyright (c) 2008 Bojan Rajkovic
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//

#include "../include/SocketWrapper.h"

IPv4ClientSocket::~IPv4ClientSocket() {
	// Close the sockets when we destroy.
	close(this->socketDescriptor);
	fclose(this->socketFileDescriptor);
	// Nothing else to clean up, no loose ptrs.
}

void IPv4ClientSocket::Open(std::string Hostname, unsigned int Port) {
	// Store the hostname and host port, just so we know what they are
	this->hostName = Hostname;
	this->hostPort = Port;
	// Open a basic IPv4, TCP/IP socket.
	this->socketDescriptor = socket(AF_INET, SOCK_STREAM, 0);
	// Also open a file descriptor that can read/write to the socket
	// Can be a better to use fgets()/fputs() than send()/recv()
	this->socketFileDescriptor = fdopen(this->socketDescriptor,"r+");
	
	if (this->socketDescriptor < 0) {
		throw SocketException(errno);
	}
	
	// Set up some temporary variables
	struct sockaddr_in serverAddress;
	struct hostent *hostInfo;
	
	// Get the hostent structure for our server
	hostInfo = gethostbyname(Hostname.c_str());
	
	// If it's null, gethostbyname couldn't resolve anything, so let's bail.
	// This is probably due to either a network error or a misconfiguration.
	// We'll throw an exception to be sure that the caller does something about it
	if (hostInfo == NULL) {
		throw SocketException(h_errno);
	}
	
	// Populate the sockaddr_in struct so we can use it for connect().
	serverAddress.sin_family = hostInfo->h_addrtype;
	// This is a pain--the field is non-assignable, so we have to memcpy the first address
	// in the hostent structure to the field.
	memcpy(&serverAddress.sin_addr.s_addr, hostInfo->h_addr_list[0], hostInfo->h_length);
	// Convert the port number to network byte order from host byte order.
	serverAddress.sin_port = htons(Port);

	// If connect returns < 0, we couldn't connect for whatever reason, so let's throw an
	// exception.
	if ( connect(this->socketDescriptor, reinterpret_cast<const sockaddr*>(&serverAddress), sizeof(serverAddress)) < 0) {
		throw SocketException(errno);
	}
	
	// If we got this far, we've probably been connected to the other end.
	// Now we're ready to send.
}

IPv4ClientSocket::IPv4ClientSocket() {}

IPv4ClientSocket::IPv4ClientSocket(std::string Hostname, unsigned int Port) {
	IPv4ClientSocket::Open(Hostname, Port);
}

int IPv4ClientSocket::Write(std::string Data) {
	// We'll just flat out send the data, there's not much to much aroud with here
	// Return the return value of send because it indicates how many bytes were sent
	// No need for flags, nothing applies.
	int retval = send(this->socketDescriptor, Data.c_str(), Data.size(), 0);
	
	if (retval < 0) {
		throw SocketException(errno);
	} else return retval;
}

byte* IPv4ClientSocket::ReadBytes(int Bytes) {
	// Set up a buffer
	byte* buffer = static_cast<byte*>(malloc(sizeof(byte)*Bytes));
	
	// We'll use recv again, with MSG_WAITALL because we want to fill our quota
	// If we got -1, there's an error, throw an exception.
	if (recv(this->socketDescriptor, buffer, Bytes, MSG_WAITALL) < 0) {
		throw SocketException(errno);
	}
	
	// No need to massage the data, just send it back.
	// Just a note, this buffer does need to be free()'ed after use.
	return buffer;
}

std::string IPv4ClientSocket::ReadLine() {
	// Again, we need to set up a buffer to read into, plus the extra byte
	// We'll allocate 4 KiB for the line, and hope it's not that long
	char* buffer = static_cast<char*>(malloc(sizeof(char)*LINE_BUFFER_SIZE));	
	
	// We'll use fgets this time, since we're largely mimicing its behavior
	buffer = fgets(buffer, sizeof(char)*LINE_BUFFER_SIZE, this->socketFileDescriptor);
	
	// If fgets gave us back NULL, we've got a problem, bail out to the caller
	if (buffer == NULL) {
		throw SocketException(errno);
	}
	
	// Build the string and ship it back
	std::string line(buffer);
	return line;
}

std::string IPv4ClientSocket::Read(int ReadSize) {
	// First, set up a buffer to read into. That extra byte is for the null terminator.
	char* buffer = static_cast<char*>(malloc(sizeof(char)*ReadSize+1));
	
	// We'll use recv with the MSG_WAITALL flag because we want to be sure we get ReadSize bytes.
	// If we got -1, there's an error, throw an exception.
	if (recv(this->socketDescriptor, buffer, ReadSize+1, MSG_WAITALL) < 0) {
		throw SocketException(errno);
	}
	
	// Well, if we got this far, we got all the data we wanted.
	// Let's make it into a std::string and give it back.
	std::string returnString(buffer);
	return returnString;
}
